Namespace Internals
	''' <summary>
	''' A class to manage Command execution and output capturing
	''' (hg.Net by Wesley Werner)
	''' </summary>
	''' <remarks></remarks>
	Public Class Command

		''' <summary>
		''' Defines the Mercurial executable. Usually set in your path environment variable, so it's globally visible.
		''' </summary>
		''' <remarks></remarks>
		Private Const hgExecutable As String = "hg.exe"

		''' <summary>
		''' A space character
		''' </summary>
		''' <remarks></remarks>
		Private Const char_space As Char = " "c

		''' <summary>
		''' A quote character
		''' </summary>
		''' <remarks></remarks>
		Private Const char_quote As Char = ControlChars.Quote

		''' <summary>
		''' The process object to handle our execution
		''' </summary>
		''' <remarks></remarks>
		Private WithEvents Proc As Diagnostics.Process



#Region " properties "


		''' <summary>
		''' The list of command parameters to execute
		''' </summary>
		''' <value></value>
		''' <returns></returns>
		''' <remarks></remarks>
		Public Property Parameters() As List(Of String)
			Get
				Return _Parameters
			End Get
			Set(ByVal value As List(Of String))
				_Parameters = value
			End Set
		End Property
		Private _Parameters As List(Of String)


		''' <summary>
		''' The working directory for command execution
		''' </summary>
		''' <value></value>
		''' <returns></returns>
		''' <remarks></remarks>
		Public Property WorkingDirectory() As String
			Get
				Return _WorkingDirectory
			End Get
			Set(ByVal value As String)
				_WorkingDirectory = value
			End Set
		End Property
		Private _WorkingDirectory As String


		''' <summary>
		''' The resulting Command output
		''' </summary>
		''' <value></value>
		''' <returns></returns>
		''' <remarks></remarks>
		Public Property Output() As String
			Get
				Return _Output
			End Get
			Set(ByVal value As String)
				_Output = value
			End Set
		End Property
		Private _Output As String


		''' <summary>
		''' The exit code of the last command
		''' </summary>
		''' <value></value>
		''' <returns></returns>
		''' <remarks></remarks>
		Public ReadOnly Property ExitCode() As Integer
			Get
				Return _ExitCode
			End Get
		End Property
		Private _ExitCode As Integer = 0


		''' <summary>
		''' The standard error text of the last command
		''' </summary>
		''' <value></value>
		''' <returns></returns>
		''' <remarks></remarks>
		Public ReadOnly Property StandardError() As String
			Get
				Return _StandardError
			End Get
		End Property
		Private _StandardError As String = ""


		''' <summary>
		''' Hide the commandline window during command execution
		''' </summary>
		''' <value></value>
		''' <returns></returns>
		''' <remarks></remarks>
		Public Property Hidden() As Boolean
			Get
				Return _Hidden
			End Get
			Set(ByVal value As Boolean)
				_Hidden = value
			End Set
		End Property
		Private _Hidden As Boolean = True


		''' <summary>
		''' Use asynchronous events to capture command output
		''' </summary>
		''' <value></value>
		''' <returns></returns>
		''' <remarks></remarks>
		Public Property ASyncOutput() As Boolean
			Get
				Return _ASyncOutput
			End Get
			Set(ByVal value As Boolean)
				_ASyncOutput = value
			End Set
		End Property
		Private _ASyncOutput As Boolean


#End Region


#Region " methods "


		''' <summary>
		''' Initialize this Command
		''' </summary>
		''' <remarks></remarks>
		Public Sub New()
			Me.Parameters = New List(Of String)
		End Sub


		''' <summary>
		''' Validate this Command
		''' </summary>
		''' <remarks>Throws an exception if something is missing</remarks>
		Private Sub Validate()
			If IsNothing(Me.WorkingDirectory) Then
				Throw New Exception("Set the WorkingDirectory for this Command")
			End If
			If Not IO.Directory.Exists(Me.WorkingDirectory) Then
				Throw New Exception(String.Format("The WorkingDirectory '{0}' does not exist", Me.WorkingDirectory))
			End If
			If IsNothing(Me.Parameters) OrElse (Me.Parameters.Count = 0) Then
				Throw New Exception("Specifiy at least one Parameter: the command to execute")
			End If
		End Sub


		''' <summary>
		''' Execute this Command
		''' </summary>
		''' <remarks>Throws an exception on error. Auto-quotes parameters with spaces.</remarks>
		''' <returns>Command output for synchronous calls, Nothing for async calls</returns>
		Function ExecuteCommand() As String

			' validate the command properties
			Validate()

			' add quotes around parameters that contain spaces
			For idx As Integer = 0 To Me.Parameters.Count - 1
				' get this p
				Dim p As String = Me.Parameters(idx)
				' if it contains a space, and no quotes
				If (p.Contains(char_space) And (Not p.StartsWith(char_quote))) Then
					' wrap the parameter around quotes
					p = String.Concat(char_quote, p, char_quote)
					Me.Parameters(idx) = p
				End If
			Next

			' clear the error flags
			_StandardError = ""
			_ExitCode = 0

			' setup the process start info
			Dim psi As New Diagnostics.ProcessStartInfo(hgExecutable, String.Join(" ", Me.Parameters.ToArray))

			' we redirect output
			psi.UseShellExecute = False
			psi.RedirectStandardOutput = True
			psi.RedirectStandardError = True
			psi.WorkingDirectory = WorkingDirectory
			psi.CreateNoWindow = Hidden

			' start the process
			Proc = Process.Start(psi)

			' start reading the output
			If (ASyncOutput) Then

				' start async reading of std output / error
				Proc.EnableRaisingEvents = True
				Proc.BeginErrorReadLine()
				Proc.BeginOutputReadLine()

				' return nothing for async calls
				Return Nothing

			Else

				' clear the output
				Me.Output = ""

				' we poll the output, becuase the WaitForExit() method will keep
				' returning false if the StandardOutput buffer is full
				While Not Proc.WaitForExit(1000)
					' append the output
					Me.Output += Proc.StandardOutput.ReadToEnd
					' allow other processing to happen
					Application.DoEvents()
				End While

				' save the exit code
				_ExitCode = Proc.ExitCode

				' capture and return the rest of the output
				If (Proc.ExitCode = 0) Then
					Me.Output += Proc.StandardOutput.ReadToEnd
					Return Me.Output
				Else
					' exit code indicates an error
					' capture standard error, and throw our toys out
					_StandardError = Proc.StandardError.ReadToEnd
					Throw New Exception(String.Format("{0} (code {1})", Me.StandardError, Proc.ExitCode))
				End If

			End If

		End Function


		''' <summary>
		''' Execute this command with the given parameters
		''' </summary>
		''' <param name="params">The command and parameters to execute</param>
		''' <remarks></remarks>
		''' <returns>Command output</returns>
		Function ExecuteCommand(ByVal ParamArray params() As String) As String
			Me.Parameters.Clear()
			Me.Parameters.AddRange(params)
			Return Me.ExecuteCommand()
		End Function


#End Region


#Region " Events "

		''' <summary>
		''' Occurs if command output is received
		''' </summary>
		''' <param name="sender"></param>
		''' <param name="e"></param>
		''' <remarks></remarks>
		Public Event OutputReceived(ByVal sender As Object, ByVal e As Internals.CommandEventArgs)

		''' <summary>
		''' Occurs after the command completes
		''' </summary>
		''' <param name="sender"></param>
		''' <remarks></remarks>
		Public Event Complete(ByVal sender As Object, ByVal e As Internals.CommandEventArgs)


		''' <summary>
		''' The process has exited, pass this on to our caller
		''' </summary>
		''' <param name="sender"></param>
		''' <param name="e"></param>
		''' <remarks></remarks>
		Private Sub proc_Exited(ByVal sender As Object, ByVal e As System.EventArgs) Handles Proc.Exited
			RaiseEvent Complete(Me, New Internals.CommandEventArgs(e))
		End Sub


		''' <summary>
		''' The process has captured output, pass this on to our caller
		''' </summary>
		''' <param name="sender"></param>
		''' <param name="e"></param>
		''' <remarks></remarks>
		Private Sub proc_OutputDataReceived(ByVal sender As Object, ByVal e As System.Diagnostics.DataReceivedEventArgs) Handles Proc.OutputDataReceived
			RaiseEvent OutputReceived(Me, New CommandEventArgs(e.Data))
		End Sub


		''' <summary>
		''' The process has captured error output, pass this on to our caller
		''' </summary>
		''' <param name="sender"></param>
		''' <param name="e"></param>
		''' <remarks></remarks>
		Private Sub proc_ErrorDataReceived(ByVal sender As Object, ByVal e As System.Diagnostics.DataReceivedEventArgs) Handles Proc.ErrorDataReceived
			RaiseEvent OutputReceived(Me, New CommandEventArgs(e.Data))
		End Sub


#End Region



	End Class
End Namespace
